package de.blitzcoder.collide.engine.parser;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;

/**
 *
 * @author blitzcoder
 */
public class CodeParser {

    private CodeTokenizer tokenizer;

    private CodeType currentType;
    private LinkedList<CodeObject> codeObjectList;

    private HashMap<String,String> returnTypes;
    
    private LinkedList<String> keywords = new LinkedList<String>();

    private int commentLines; // For the file-info window
    
    private boolean inComment = false; // true if currently in a rem-block
    private int commentLine = 0; // The start-line of the comment
    private String  comment = ""; // the actual comment
    
    private boolean ignorePrivate; // Ignore stuff marked as private, disabled by default
    private boolean inPrivate = false;

    private File file;

    private TokenizedCode code;

    private int strictMode;

    public static final int STRICT = 1;
    public static final int NO_STRICT = 0;
    public static final int SUPERSTRICT = 2;

    public CodeParser() {
        this(NO_STRICT, false);
    }

    public CodeParser(boolean ignorePrivate) {
        this(NO_STRICT,ignorePrivate);
    }

    public CodeParser(int strict, boolean ignorePrivate) {
        this.ignorePrivate = ignorePrivate;
        this.strictMode = strict;
        tokenizer = new CodeTokenizer();
        codeObjectList = new LinkedList<CodeObject>();
        currentType = null;
        returnTypes = new HashMap<String, String>();
        addReturnTypes();
    }
    
    private void addReturnTypes() {
        returnTypes.clear();
        returnTypes.put("$","String");
        returnTypes.put("!","Double");
        returnTypes.put("#","Int");

        if (strictMode != SUPERSTRICT)
            returnTypes.put("","Int");
        else
            returnTypes.put("","Void");


        returnTypes.put("%","Float");
        returnTypes.put("$z","CString");
    }
    
    public LinkedList<CodeObject> getResult() {
        return codeObjectList;
    }
    
    public LinkedList<String> getKeywords() {
        return keywords;
    }

    public int getCommentLines() {
        return commentLines;
    }
    
    public void parse(String code) {
        parse(tokenizer.tokenize(code,true),null);
    }

    public void parse(String code,File file) {
        parse(tokenizer.tokenize(code,true),file);
    }
    
    public void parse(TokenizedCode code) {
        parse(code,null);
    }

    public void parse(TokenizedCode code,File file) {
        
        this.code = code;
        this.file = file;

        
        commentLines = 0;
        
        String[][] token = code.token;
        
        //JOptionPane.showMessageDialog(null, Log.getStackTrace());
        
        // Clear old results
        codeObjectList.clear();
        
        // Iterate through lines
        for (int line=0;line<token.length;line++) {
            
            String[] t = token[line];
        
            // Check if the line is not empty
            if (t.length > 0) {
                
                parseLine(t,code.lines[line]);
                
            }
            
        }
    
    }

    
    /*
     * Now check for various types of CodeObject's
     */
    private void parseLine(String[] token,int line) {
        
        if (token.length == 0)
            return;
        
        // Check if line is an endtype
         if (token[0].equalsIgnoreCase("end")) {
            if (token.length > 1) {
                if (token [1].equalsIgnoreCase("type")) {
                    currentType = null;
                } else if (token[1].equalsIgnoreCase("rem")) {
                    inComment = false;
                }
            }
            // Always return if line is "end*"
            return;
        } else if (token[0].equalsIgnoreCase("endtype")) {
            currentType = null;
            return;
        } else if (token[0].equalsIgnoreCase("endrem")) {
            inComment = false;
            return;

        // Check for Strict/Superstrict
        // Wenn Strict oder SuperStrict aktiviert sind,
        // haben Funktionen/Methoden ohne Angabe des Rückgabetyps
        // keine Rückgabe. Ohne Strict hingehen geben sie int zurück
        } else if (token[0].equalsIgnoreCase("strict")) {
            this.strictMode = STRICT;
            addReturnTypes();
        }  else if (token[0].equalsIgnoreCase("superstrict")) {
            this.strictMode = SUPERSTRICT;
            addReturnTypes();
        }

        // Private/Public
        else  if (token[0].equalsIgnoreCase("private")) {
            inPrivate = true;
        } else  if (token[0].equalsIgnoreCase("public")) {
            inPrivate = false;
        }
        
        // Single line comment
        //System.out.println("Token: -"+token[0]+"-");
        if (token[0].startsWith("'")) {
            comment += code.originalText[line].substring(1)+"\n";
            return;
        }
        
        if (token[0].equalsIgnoreCase("rem")) {
            inComment = true;
            commentLine = line;
            return;
        }
        
        // Check if currently in a REM-Block
        // If true, just add to the comments String
        if (inComment) {

            // Check if its a keyword docComment (See examples in brl/blitz/blitz.bmx)
            if (code.originalText[line].trim().startsWith("keyword:")) {
                String keyword = code.originalText[line].trim().substring(8).trim().replace("\"","");
                //System.out.println("Found Keyword: "+keyword);
                keywords.addLast(keyword);
                inComment = false; // Ignore the rest of the comment
                return;
            }


            comment+=code.originalText[line]+"\n";
            commentLines++;
            return;
        }
        
        
        CodeObject object = null;
        LinkedList<CodeGlobal> globals = null;
        LinkedList<CodeConstant> constants = null;
        LinkedList<CodeField> fields = null;
        // Now search
        // Only parse the stuff if its private or private is ignored
        if (!ignorePrivate || !inPrivate) {
            if (token[0].equalsIgnoreCase("function")) {
                object = parseFunction(token,line);
            } else if (token[0].equalsIgnoreCase("const")) {
                constants = parseConstant(token,line);
            } else if (token[0].equalsIgnoreCase("global")) {
                globals = parseGlobal(token,line);
            } else if (token[0].equalsIgnoreCase("method")) {
                object = parseMethod(token,line);
            } else if (token[0].equalsIgnoreCase("field")) {
                fields = parseField(token,line);
            } else if (token[0].equalsIgnoreCase("type")) {
                object = parseType(token,line);
            } else if (token[0].equalsIgnoreCase("include") || token[0].equalsIgnoreCase("import")) {
                object = parseInclude(token,line);
            } else if (token[0].equalsIgnoreCase("moduleinfo")) {
                object = parseModuleInfo(token,line);
            }
        }
        
        if (object != null) {
            if (object instanceof CodeType) {
                currentType = (CodeType)object;
                codeObjectList.addLast(object);
            } else if (currentType == null) {
                codeObjectList.addLast(object);
            } else {
                currentType.getChildren().addLast(object);
                object.setParentType(currentType);
            }
            object.setComment(comment);
            object.setFile(file);
        }
        
        if (globals != null) {
            ListIterator<CodeGlobal> it = globals.listIterator();
            while (it.hasNext()) {
                CodeGlobal global = it.next();
                if (currentType != null) {
                    global.setParentType(currentType);
                    currentType.getChildren().addLast(global);
                } else {
                    codeObjectList.addLast(global);
                }
                global.setComment(comment);
                global.setFile(file);
            }
        }
        
        if (constants != null) {
            ListIterator<CodeConstant> it = constants.listIterator();
            while (it.hasNext()) {
                CodeConstant con = it.next();
                if (currentType != null) {
                    con.setParentType(currentType);
                    currentType.getChildren().addLast(con);
                } else {
                    codeObjectList.addLast(con);
                }
                con.setComment(comment);
                con.setFile(file);
            }
        }
        
        if (fields != null) {
            ListIterator<CodeField> it = fields.listIterator();
            while (it.hasNext()) {
                CodeField field = it.next();
                if (currentType != null) {
                    field.setParentType(currentType);
                    currentType.getChildren().addLast(field);
                } else {
                    codeObjectList.addLast(field);
                }
                field.setComment(comment);
                field.setFile(file);
            }
        }
        
        
        comment = "";
        
    }
    
    
    /*
     * Parse a global variable declaration
     */
    private LinkedList<CodeGlobal> parseGlobal(String[] token,int line) {
        if (token.length>1) {
            LinkedList<CodeVariable> vars = parseVariables(Arrays.copyOfRange(token,1, token.length),line);
            LinkedList<CodeGlobal> output = new LinkedList<CodeGlobal>();
            ListIterator<CodeVariable> it = vars.listIterator();
            while (it.hasNext()) {
                CodeVariable var = it.next();
                output.addLast(new CodeGlobal(var.getName(),line, var.getType(), var.getValue()));
            }
            return output;
        }
        return null;
    }
    
    /*
     * Parse a ModuleInfo line
     */
    private CodeObject parseModuleInfo(String[] token,int line) {
        if (token.length > 1) {
            String moduleInfo = "";
            for (int i=1;i<token.length;i++) {
                if (token[i].startsWith("\"")) {
                    moduleInfo+=token[i].replace("\"", "");
                }
            }
            return new CodeModuleInfo(moduleInfo, line);
        }
        return null;
    }
    
    /*
     * Parse a field variable declaration
     */
    private LinkedList<CodeField> parseField(String[] token,int line) {
        if (token.length>1) {
            LinkedList<CodeVariable> vars = parseVariables(Arrays.copyOfRange(token,1, token.length),line);
            LinkedList<CodeField> output = new LinkedList<CodeField>();
            ListIterator<CodeVariable> it = vars.listIterator();
            while (it.hasNext()) {
                CodeVariable var = it.next();
                output.addLast(new CodeField(var.getName(),line, var.getType(), var.getValue()));
            }
            return output;
        }
        return null;
    }
    
    /*
     * Parse a const variable declaration
     */
    private LinkedList<CodeConstant> parseConstant(String[] token,int line) {
        if (token.length>1) {
            LinkedList<CodeVariable> vars = parseVariables(Arrays.copyOfRange(token,1, token.length),line);
            LinkedList<CodeConstant> output = new LinkedList<CodeConstant>();
            ListIterator<CodeVariable> it = vars.listIterator();
            while (it.hasNext()) {
                CodeVariable var = it.next();
                output.addLast(new CodeConstant(var.getName(),line, var.getType(), var.getValue()));
            }
            return output;
        }
        return null;
    }
    
    /*
     * Parse and include statement
     */
    private CodeObject parseInclude(String[] token,int line) {
        /*
        System.out.println("Include:");
        for (String t:token) {
            System.out.println("-"+t+"-");
        }*/
        for (String t:token)
            if (t.startsWith("\""))
                if (t.endsWith(".bmx\""))
                    return new CodeInclude(t.replace("\"", ""), line);
        
        return null;
    }
    
    /*
     * Parse a list of variables separated by commas
     */
    private LinkedList<CodeVariable> parseVariables(String[] token,int line) {
        int start = 0;
        
        /*
        System.out.print("Variable:");
        for (int i=0;i<token.length;i++) {
            System.out.print(token[i]+"-");
        }
        System.out.println();
        */
        
        LinkedList<CodeVariable> list = new LinkedList<CodeVariable>();
        int openbracks = 0; // Arrys like foo$[] = ["a","b"]
        for (int i=0;i<token.length;i++) {
            if (token[i].equals("[") || token[i].equals("(")) {
                openbracks++;
            } else if (token[i].equals("]") || token[i].equals(")")) {
                openbracks--;
            } else if (openbracks == 0 && token[i].equals(",")) {
                CodeVariable var = parseVariable( Arrays.copyOfRange(token, start, i),line );
                if (var != null)
                    list.addLast(var);
                start = i+1;
            }
        }
        if (start<=token.length) {
            CodeVariable var = parseVariable(Arrays.copyOfRange(token, start, token.length),line);
            if (var != null)
                list.addLast(var);
        }
        
        return list;
    }
    
    /*
     * Parse a spingle Variable like "a:String=foo"
     */
    private CodeVariable parseVariable(String[] token,int line) {
        
        if (token.length == 0)
            return null;
        
        String varName  = token[0];
        String varType  = "";
        String varValue = "";
        
        /*
        System.out.print("Var: ");
        for (int i=0;i<token.length;i++)
            System.out.print(token[i]);
        System.out.println();
         */
        
        if (token.length>1) {
            
            // Find return type
            for (int i=1;i<token.length;i++) {
                if ((token[i]).equals("="))
                        break;
                varType+=token[i];
            }
            
            if (varType.startsWith(":")) {
                varType = varType.substring(1);
            } else {
                String type = returnTypes.get(varType.replace("[", "").replace("]", "")); // Arrays
                if (type != null) 
                    varType = type;
            }
            
            /*
            if (token[1].equals(":")) {
                if (token.length>2)
                    varType = token[2];
            } else {
                varType = returnTypes.get(token[1]);
            }
            */
            
            // Find Standard value
            if (token[1].equals(":")) {
                if (token.length>4) {
                    for (int i=4;i<token.length;i++)
                        varValue+=token[i];
                }
            } else {
                if (token.length>3) {
                    for (int i=3;i<token.length;i++)
                        varValue+=token[i];
                }
            }
            
        } else {
            varType = returnTypes.get("");
        }
        

        
        return new CodeVariable(varName,line, varType, varValue);
    }
    
    /*
     * Parse a type declaration
     */
    private CodeType parseType(String[] token,int line) {
        
        if (token.length>1) {
            return new CodeType(token[1],line);
        }
        
        return null;
    }
    
    /*
     * Parse a line with a fuction declaration
     */
    private CodeFunction parseFunction(String[] token,int line) {
        
        if (token.length<2)
            return null;
        
        String funcName = token [1];
        String funcType = getFunctionReturnType(token);
        
        LinkedList<CodeParameter> params = getFunctionParameters(token);
        
        CodeFunction func = new CodeFunction(funcName,line,funcType);
        func.setParameterList(params);
        
        return func;
        
    }
    
    private String getFunctionReturnType(String[] token) {
        
        if (token.length<3)
            return null;
        
        // Find func-return-type
        if (token[2].equals(":")) {
            if (token.length>3) {
                return token[3];
            }
        } else if (token[2].equals("(")) {
            return returnTypes.get("");
        } else {
            /*
            if (token.length>3) {
                return returnTypes.get(token[3]);
            }*/
            String returnType = returnTypes.get(token[2]);
            if (returnType == null)
                returnType = "";
            return returnType;
        }
        return "";
    }
    
    private CodeMethod parseMethod(String[] token,int line) {
        
        if (token.length<2)
            return null;
        
        String methName = token[1];
        String methType = getFunctionReturnType(token);
        
        LinkedList<CodeParameter> params = getFunctionParameters(token);
        
        CodeMethod meth = new CodeMethod(methName,line,methType);
        
        meth.setParameterList(params);
        
        return meth;
    }
    
    private LinkedList<CodeParameter> getFunctionParameters(String[] token) {

        for (String s:token)
            if (s.equals("CreateWindow")) {
                printArray(token);
                break;
            }
        
        // Now find start and end bracket
        int start       = 0;
        int end         = 0;
        int openbracks  = 0;
        for (int i=2;i<token.length;i++) {
            
            if (start==0 && token[i].equals("(") ) {
                start = i;
            }
            
            if (token[i].equals("(")) {
                openbracks++;
            }
            
            if (token[i].equals(")")) {
                openbracks--;
                if (openbracks == 0) {
                    end = i;
                }
            }
            
        }
        
        LinkedList<String> paramList = new LinkedList<String>();
        
        if (start != 0 && end != 0) {
            
            int paramStart = start+1;
            
            for (int i=start;i<=end;i++) {
                
                if (token[i].equals(",")) {
                    
                    StringBuffer buf = new StringBuffer();
                    for (int i_=paramStart;i_<i;i_++)
                        buf.append(token[i_]);
                    
                    String out = buf.toString();
                    if (out.length()>0)
                        paramList.addLast(out);
                    
                    paramStart = i+1;
                }
                
            }
            
            // Do the last one
            StringBuffer buf = new StringBuffer();
            for (int i_=paramStart;i_<end;i_++)
                buf.append(token[i_]);
            String outp = buf.toString();
            if (outp.length()>0)
                paramList.addLast(outp);
            
        }
        
        return parseFunctionParameters(paramList);
        
    }
    
    // Parse Function parameters. params contains things like "foo:int", "lala$" or "uschu:Double=12.3"
    private LinkedList<CodeParameter> parseFunctionParameters(LinkedList<String> params) {
        
        LinkedList<CodeParameter> output = new LinkedList<CodeParameter>();
        
        ListIterator<String> it = params.listIterator();
        while(it.hasNext()) {
            char[] str = it.next().toCharArray();
            
            String paramName="";
            String paramType="";
            String paramStandardValue="";
            
            // First find the parametername
            for (int i=0;i<str.length;i++) {
                char c = str[i];
                if (!Character.isLetterOrDigit(c)) {
                    paramName = new String(Arrays.copyOfRange(str, 0, i));
                    str = Arrays.copyOfRange(str, i, str.length);
                    break;
                }
            }
            if (paramName.length() == 0) {
                paramName = new String(Arrays.copyOfRange(str, 0, str.length));
                str = new char[1];
                str[0] = ' ';
            }
            
            // Now get the return type
            if (str.length>0 && str[0] == 58) {
                // Find the end of the param
                int end=0;
                for (int i=0;i<str.length;i++) {
                    if (str[i] == 61)  {// ASCII 61 = "="
                        end=i;
                        break;
                    }
                }
                if (end == 0)
                    end = str.length;
                paramType = new String(Arrays.copyOfRange(str,1,end));
            } else if ( returnTypes.get(""+str[0]) != null ) {
                paramType = returnTypes.get(""+str[0]);
            } else {
                paramType = returnTypes.get("");
            }
            
            
            // Try to find a Standard-Value
            for (int i=0;i<str.length;i++) {
                if (str[i] == 61) {// ASCII 61 = "="
                    paramStandardValue = new String(Arrays.copyOfRange(str, i+1, str.length));
                }
            }
            
            output.addLast(new CodeParameter(paramName, paramType, paramStandardValue));
            
        }
        
        /*
        ListIterator<CodeParameter> itr = output.listIterator();
        while (itr.hasNext()) {
            CodeParameter param = itr.next();
            System.out.println("----------------------");
            System.out.println("Name: "+param.getName());
            System.out.println("Type: "+param.getType());
            System.out.println("Value: "+param.getValue());    
        }
        */
        
        return output;
    }
    
    
    private void print() {
        // Print
            ListIterator<CodeObject> it = getResult().listIterator();
            while (it.hasNext()) {
                
                CodeObject obj = it.next();
                
                if (obj instanceof CodeConstant)
                    System.out.println("Const "+obj.getName());
                
                if (obj instanceof CodeFunction)
                    System.out.println("Function "+obj.getName());
                
                if (obj instanceof CodeGlobal)
                    System.out.println("Global "+obj.getName());
                
                if (obj instanceof CodeMethod)
                    System.out.println("Method "+obj.getName());
                
                if (obj instanceof CodeType) {
                    System.out.println("Type "+obj.getName());
                    
                    ListIterator<CodeObject> it_ = ((CodeType)obj).getChildren().listIterator();
                    while (it_.hasNext()) {
                        CodeObject obj_ = it_.next();
                        
                        if (obj_ instanceof CodeConstant)
                            System.out.println("---> Const "+obj_.getName());

                        if (obj_ instanceof CodeFunction)
                            System.out.println("---> Function "+obj_.getName());

                        if (obj_ instanceof CodeGlobal)
                            System.out.println("---> Global "+obj_.getName());

                        if (obj_ instanceof CodeMethod)
                            System.out.println("---> Method "+obj_.getName());
                        
                    }
                    
                }
                
            }
    }
    
    /*
     * Simple Method to print a String[]
     */
    private void printArray(String[] arr) {
        for (int i=0;i<arr.length;i++)
            System.out.print("["+arr[i]+"]");
        System.out.println();
    }
    private void printArray(String[] arr,int start,int end) {
        System.out.print(start+"-"+end);
        for (int i=start;i<=end;i++)
            System.out.print(arr[i]);
        System.out.println();
    }
    
}
